package de.unimannheim.ki.usermanagement;

import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URL;



import weka.associations.FPGrowth;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.experiment.InstanceQuery;

import com.google.gson.Gson;



import de.umass.lastfm.PaginatedResult;
import de.umass.lastfm.Track;
import de.umass.lastfm.User;
import de.unimannheim.ki.algorithmn.IComparator;
import de.unimannheim.ki.algorithmn.LikeAssociationJoint;
import de.unimannheim.ki.algorithmn.LikeDisJoint;
import de.unimannheim.ki.algorithmn.LikeHammingJoint;
import de.unimannheim.ki.algorithmn.NNAlgorithm;
import de.unimannheim.ki.algorithmn.Neighbour;
import de.unimannheim.ki.databaseentities.JSONSong;
import de.unimannheim.ki.databaseentities.MusicObject;
import de.unimannheim.ki.databaseentities.Musicuser;
import de.unimannheim.ki.databaseentities.Song;
import de.unimannheim.ki.servlets.JsonAnswer;
import de.unimannheim.ki.servlets.JsonError;
import de.unimannheim.ki.servlets.JsonEvaluateAnswer;
import de.unimannheim.ki.servlets.JsonEvaluateElem;
import de.unimannheim.ki.databaseentities.UserEvaluation;



public class UserManager {
	
	
	//UserSongs Rating matrix
	private int[][] userSongMatrix;
	
	//Mapping Components
	//userMapping
	
	TIntObjectHashMap<String> userMappingInttoFBID;
	//HashMap<String, Integer> userMappingFBIDtoInt;
	TObjectIntHashMap<String> userMappingFBIDtoInt;
	//HashMap<Integer, String> userMappingInttoFBID;
	//songMapping
	
	
	//HashMap<String, Integer> songMappingIDtoInt;
	//HashMap<Integer, String> songMappingInttoID;
	
	TObjectIntHashMap<String> songMappingIDtoInt;
	TIntObjectHashMap<String> songMappingInttoID;
	private FacebookUserHandler fbUserHandler;
	private LastFMUserHandler lastfmHandler;
	private DatabaseUserDAO userDao;
	//private FPGrowth model;
	
	public static final int LIKEJOINT = 0;
	public static final int LIKEHAMMINGJOINT =1;
	public static final int LIKEASSOCIATIONJOINT = 2;
	private HashMap<Integer,IComparator> comparators;

	public UserManager() {
		
		comparators  = new HashMap<Integer,IComparator>();
		
		/*
		 * uncomment Weka stuff
		Instances data = null;
		InstanceQuery query = null;
			try {
			
				query = new InstanceQuery();
				query.setDatabaseURL("jdbc:mysql://aa6bvx41gtnv7k.cwluancabhdk.eu-west-1.rds.amazonaws.com:3306/ebdb");
				System.out.println("DATABASE URL:::::"+query.getDatabaseURL());
				 query.setUsername("test");
				 query.setPassword("testtest");
				 query.setQuery("select * from testdata");
				query.connectToDatabase();
				data = query.retrieveInstances();
				// DataSource source = new DataSource("C:/Users/Alex/workSpaceProjekt/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/wtpwebapps/my-music-search/sample5.csv");
				// DataSource source = new DataSource(csvfile);
				  query.disconnectFromDatabase();
				  query.close();
				//data = source.getDataSet();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			
		*/
		userDao = DatabaseUserDAO.getDAO();
		fbUserHandler = new FacebookUserHandler();
		lastfmHandler = new LastFMUserHandler();
	
		/*
		 * uncomment Weka stuff
		FPGrowth model = new FPGrowth();
		model.setDelta(0.01);
	    model.setLowerBoundMinSupport(0.2);
	    model.setMinMetric(0.5);
	     
		model.setFindAllRulesForSupportLevel(true);
		model.setNumRulesToFind(100);
			try {
			model.buildAssociations(data);
			System.out.println("Model build!");
		} catch (Exception e) {
			e.printStackTrace();
		}*/
		comparators.put(LIKEJOINT, new LikeDisJoint());
		comparators.put(LIKEHAMMINGJOINT,new LikeHammingJoint());
		//comparators.put(LIKEASSOCIATIONJOINT, new LikeAssociationJoint(model, userDao));
		
		// Build the User song Matrix
		System.out.println("Build song user matrix");
		// First build Mapping for Songs and Users;
		userMappingFBIDtoInt =  new TObjectIntHashMap<String>();
		userMappingInttoFBID =  new TIntObjectHashMap<String>();
		
		
		songMappingIDtoInt = new TObjectIntHashMap<String>();
		songMappingInttoID = new TIntObjectHashMap<String>();
		List<Musicuser> users = userDao.getAllUserswithLastfm();
		List<Song> songs = userDao.getAllSongs();
		int x= 0;
		for (Song song : songs) {
			songMappingIDtoInt.put(song.getId(),x);
			songMappingInttoID.put(x, song.getId());
			x++;
		}
		System.out.println("Step 1 done");
		userSongMatrix = new int[songs.size()][users.size()];
		List<Song> usersongs;
		int i = 0;
		int songid;
		for (Musicuser musicuser : users) {
			if(musicuser.getLastfmid() == null) continue;
			userMappingFBIDtoInt.put(musicuser.getFbid(), i);
			usersongs = musicuser.getSongs();
			//get all songs for user and save them in the matrix
			for (Song song : usersongs) {
				songid = songMappingIDtoInt.get(song.getId());
				userSongMatrix[songid][i] = 1;
			}
			i ++;
		}
		System.out.println("Step 2 done + ++" + i);
		
		
	}

	public void registerUser(String accessToken, String lastFmUser){
	
		FbUser user = fbUserHandler.getUser("me()",accessToken);
		
		List<de.umass.lastfm.Track> usertracks = lastfmHandler.getUserTracks(lastFmUser);
		userDao.persistNewUser(user, usertracks, lastFmUser);
		
	}
	
	public void refreshRegisteredUser(String accessToken){
		FbUser fuser;
		
		for (Musicuser user : userDao.getAllUserswithLastfm()) {
			fuser = fbUserHandler.getUser(user.getFbid(), accessToken);
			
			System.out.println(fuser.getMovies() + fuser.getMusic());
			List<MusicObject> objects= new ArrayList<MusicObject>();
			
			
	
		  		System.out.println("TEST TEST TEST TEST TEST");	
		 	String[] splitter = fuser.getMusic().split(",");
		 	MusicObject object = null;
		 	
		 	for (String string : splitter) {
		 		//first check if the object is in the database
		 		if(string.trim().equals("")) continue;
		 		if(string.contains("'")) continue;
		 		if(userDao.objectExits(string.trim())){
		 			object = userDao.getObject(string.trim().replace("'", "_"));
		 		} else {
		 			object = new MusicObject();
			 		object.setName(string.trim().replace("'", "_"));
			 		object.setType("music");
		 		}
				objects.add(object);
			}
		 	
		 	 splitter = fuser.getMovies().split(",");
		 	 
		  	for (String string : splitter) {
		  		if(string.trim().equals("")) continue;
		  		if(string.contains("'")) continue;
		 		//first check if the object is in the database
		 		if(userDao.objectExits(string.trim())){
		 			object = userDao.getObject(string.trim().replace("'", "_"));
		 		} else {
		 			object = new MusicObject();
			 		object.setName(string.trim().replace("'", "_"));
			 		object.setType("movies");
		 		}
				objects.add(object);
			}
		 	
		  	
		 	 splitter = fuser.getBooks().split(",");
		  	
		 	 for (String string : splitter) {
		 		if(string.trim().equals("")) continue;
		 		if(string.contains("'")) continue;
		 		//first check if the object is in the database
		 		if(userDao.objectExits(string.trim())){
		 			object = userDao.getObject(string.trim().replace("'", "_"));
		 		} else {
		 			object = new MusicObject();
			 		object.setName(string.trim().replace("'", "_"));
			 		object.setType("books");
		 		}
				objects.add(object);
			}
		 	
		 	 
		 	 splitter = fuser.getTv().split(",");
		 	 
		  	for (String string : splitter) {
		  		if(string.trim().equals("")) continue;
		  		if(string.contains("'")) continue;
		 		//first check if the object is in the database
		 		if(userDao.objectExits(string.trim())){
		 			object = userDao.getObject(string.trim().replace("'", "_"));
		 		} else {
		 			object = new MusicObject();
			 		object.setName(string.trim().replace("'", "_"));
			 		object.setType("tv");
		 		}
				objects.add(object);
			}
		  //get songs for the user
			
		
			
			HashMap<String, Track> hmap = new HashMap<String, Track>();
			List<Track> usertracks = new ArrayList<Track>();
			PaginatedResult<Track> resultset;
			Collection<Track> intermediate;
			int page = 1;
		
		
			
			usertracks = new ArrayList<Track>();
			
			try {
				resultset = User.getRecentTracks(user.getName(), page, 200,  LastFMAPIHandler.APIKEY);
			} catch(Exception e){
				e.printStackTrace();
				continue;
			}
				intermediate = resultset.getPageResults();
				
				for (Track track : intermediate) {
					if( ! hmap.containsKey(track.getUrl())){
						if(track.getUrl()!= null){
					hmap.put(track.getUrl(),track);
					usertracks.add(track);
					}
					}
				}
		


				resultset = User.getLovedTracks(user.getName(), LastFMAPIHandler.APIKEY);
	
			intermediate = resultset.getPageResults();
			for (Track track : intermediate) {
			
			if( ! hmap.containsKey(track.getUrl())){
				if(track.getUrl()!= null){
					hmap.put(track.getUrl(),track);
					usertracks.add(track);
				}
			}
		}
		
		
		
		//set tracks of last fm
		
		List<Song> songs = new ArrayList<Song>();
	 	Song song = null;
	 	//Convert Track list to Song list
	  	for (Track track : usertracks) {
	  		//first check if the song is in the database
	  		
	  		if(userDao.songExists(track.getUrl())) {
	  			song = userDao.getSong(track.getUrl());
	  		} else {
	  			song = new Song();
	  			
	  			if(track.getUrl() == null || track.getUrl().equals("")){
	  				continue;
	  			}else {
	  				song.setId(track.getId());
	  			}
	  			song.setId(track.getUrl());
	  			song.setInterpret(track.getArtist());
	  			song.setName(track.getName());
	  			song.setGenre("music");
	  		}
	  		songs.add(song);
		}
	  	
	  	if(  objects.size() == 0){
			NNAlgorithm nn = new NNAlgorithm(new LikeDisJoint());
			List<Neighbour> neighbours = nn.computeNeighbours(user);
			Neighbour n = null;
			for (Neighbour neighbour : neighbours) {
				n = neighbour;
				if( neighbour.getUser().getObjects().size() > 1) continue;
			}
			objects = n.getUser().getObjects();
		} 
	  		user.setSongs(songs);
		  	System.out.println("Size" + objects.size());
		 	 user.setObjects(objects);
			userDao.refreshUser(user);
		}
	}
	
	private void getUser(String fbid){
		userDao.getUser(fbid);
	}

	public boolean isregistered(String accessToken){
		//get user
		FbUser checkuser = fbUserHandler.getUser("me()",accessToken);
		System.out.println("Check registration for user" + checkuser.getUid());
		return (userDao.hasUser(checkuser.getUid().trim()));		
	}
	
	public List<JSONSong> getRelatedTracksforUser(String fbid, int algorithm, int start, int offset, double threshold) throws Exception{
		
		//Get algorithm
		IComparator comparealgorithm = comparators.get(algorithm);
		
		// Get user to compare with
		NNAlgorithm nn = new NNAlgorithm(comparealgorithm);
		Musicuser user= userDao.getUser(fbid);
		
		System.out.println("Compute Tracks");
		List<Neighbour> neighbours = nn.computeNeighbours(user);
		
		List<Song> recommendedSongs = new ArrayList<Song>();
		if(neighbours.size()> 50){
			neighbours = neighbours.subList(0,50);
		}
	
		//get the best average songs
		recommendedSongs = this.getrelatedSongs(neighbours,threshold);
		//recommendedSongs.removeAll(user.getSongs());
		
		//Short Summary for debugging
		System.out.println("-------Recommended Songs Query Result----------");
		System.out.println("Alogrithm used:" + algorithm);
		System.out.println("Threshold:" +threshold);
		System.out.println("Songs recommended:" + recommendedSongs.size());
		
		if(recommendedSongs.size() > start+offset){
			recommendedSongs = recommendedSongs.subList(start, offset);
		}else{
			throw new Exception();
		}

		List<JSONSong> jSongs = new ArrayList<JSONSong>();
		JSONSong jSong  = null;
		for (Song song : recommendedSongs) {
			jSong = new JSONSong(song.getId(), song.getGenre(), song.getInterpret(), song.getName());
			jSongs.add(jSong);
		}
	
		return jSongs;
	
	}
	
	public List<Song> getrelatedSongs(List<Neighbour> neighbours, double threshold){
		List<Song> recommended_songs = new ArrayList<Song>();
		int internaluserid;
		int internalsongid;
		
		double averagerating;
		int aggregatedrating = 0; 
		//numer of users
		int n = 0;
		//calculate the average rating for each song in the database
		for(Song song : userDao.getAllSongs()){
			internalsongid = songMappingIDtoInt.get(song.getId());
			
			for (Neighbour neighbour :neighbours) {
				//add up value from the song matrix
				aggregatedrating =  aggregatedrating +userSongMatrix [internalsongid] [userMappingFBIDtoInt.get(neighbour.getUser().getFbid())];
				n++;
			}
			
			averagerating = (double) ((double)aggregatedrating)/((double)n);
			//System.out.println(averagerating);
			n=0;
			aggregatedrating = 0; 
			if(averagerating > threshold){
				// get song for user
				recommended_songs.add(song);
			}
		}
		Collections.shuffle(recommended_songs);
		return recommended_songs;
	}
	
	public String getJSONStringforUsers(String fbid, int algorithm, int start,int offset, double threshold){
		
		Gson gson = new Gson();
		JsonAnswer ja = null;
		try{
			List<JSONSong>  songs = getRelatedTracksforUser(fbid, algorithm, start, offset, threshold);
			ja = new JsonAnswer(0, songs , null, songs.size());
		}catch (Exception e){
			e.printStackTrace();
			ja = new JsonAnswer(1, null, JsonError.generateOffsettoHigh(), 0);
		}
		return gson.toJson(ja);	
	}
	
	public void persistRating(String fbid, JsonEvaluateAnswer jea){
		Musicuser mu =userDao.getUser(fbid);

		userDao.persistRating(mu, jea);
	}

}
